#lang planet plai/plai

;; Datatypes

(define-type Expr 
  [num (n number?)] 
  [id (v symbol?)] 
  [bool (b boolean?)] 
  [bin-num-op (op procedure?) (lhs Expr?) (rhs Expr?)] 
  [iszero (e Expr?)] 
  [bif (test Expr?) (then Expr?) (else Expr?)] 
  [with (bound-id symbol?) (bound-body Expr?) (body Expr?)] 
  [fun (arg-id symbol?) 
       (arg-type Type?) (result-type Type?) 
       (body Expr?)] 
  [app (fun-expr Expr?) (arg-expr Expr?)] 
  [nempty] 
  [ncons (first Expr?) (rest Expr?)] 
  [nfirst (e Expr?)] 
  [nrest (e Expr?)] 
  [isnempty (e Expr?)]) 

(define-type Type 
  [t-num] 
  [t-bool] 
  [t-nlist] 
  [t-fun (arg Type?) (result Type?)])

(define-type Env
  [mtEnv]
  [anEnv (name symbol?) (type Type?) (env Env?)])
  

;; Operator Table

;; Contract: op-table : (listof (list/c symbol? (number? number? . -> . number?)))

;; Purpose: mapping from operator symbols to their definitions

;; Example: (define op-table (list (list '+ +))) maps '+ symbol to the "+" arithmetical operation

;; Definition [refines the header]
(define op-table
  (list (list '+ +)(list '- -)(list '* *)))

;; Tests:
(display "===== Op-table tests =====\n")
(test (first (first op-table)) '+)
(test (second (first op-table)) +)


;; Contract: lookup-op symbol -> (or procedure boolean)

;; Purpose: to extract the arithmetical operation from the operator table based on the operator's symbol, or return false if the operator is not defined in the table.

;; Example: (lookup-op '+) should return "+" arithmetical operation,  (lookup-op '!) should return false, because the operator "!" is not defined in the table

;; Definition [refines the header]
(define (lookup-op op)
  (if (list? (assoc op op-table))
      (second (assoc op op-table))
      false))

;; Tests:
(display "\n====== Lookup-op tests ======\n")
(test (lookup-op '+) +)
(test (lookup-op '-) -)
(test (lookup-op '*) *)
(test (lookup-op '/) false)
(test (lookup-op '!) false)
(test (lookup-op '$) false)



;; Contract:  parse : s-expression -> Expr 

;; Purpose: consumes the concrete representation of a program, and returns its abstract syntax tree

;; Example: (parse '{+ 1 2}) should return (bin-num-op + (num 1) (num 2))

;; Definition [refines the header]
(define (parse sexp)
  (cond
    [(number? sexp) (num sexp)]                      ; parsing number
    [(boolean? sexp) (bool sexp)]                    ; parsing boolean
    [(symbol? sexp)                                  ; parsing id
     (if(or
         (symbol=? sexp '+)
         (symbol=? sexp '-)
         (symbol=? sexp '*)
         (symbol=? sexp 'iszero)
         (symbol=? sexp 'bif)
         (symbol=? sexp 'with)
         (symbol=? sexp 'fun)
         (symbol=? sexp 'ncons)
         )
        (error 'parse "Invalid id")
        (cond
          [(symbol=? sexp 'true) (bool true)]
          [(symbol=? sexp 'false) (bool false)]
          [(symbol=? sexp 'nempty) (nempty)]
          [(symbol=? sexp 'number) (t-num)]
          [(symbol=? sexp 'boolean) (t-bool)]
          [(symbol=? sexp 'nlist) (t-nlist)]
          [true (id sexp)]))
     ]
    [(list? sexp)                                    ; parsing binding and +/- operations
     (if (= (length sexp) 1)
         (error 'parse "Invalid syntax: no operator is allowed without arguments")
         (if (= (length sexp) 2)                         ; processing operators that take 1 argument
             (cond
               [(equal? (first sexp) 'with) (error 'parse "with can't take less than 2 argument")]
               [(equal? (first sexp) 'ncons) (error 'parse "ncons can't take less than 2 arguments")]
               [(equal? (first sexp) 'bif) (error 'parse "bif takes exactly 3 arguments")]
               [(equal? (first sexp) 'iszero) (iszero (parse(second sexp)))]
               [(equal? (first sexp) 'fun) (error 'parse "fun: Invalid syntax")]
               [(equal? (first sexp) 'nfirst) (nfirst (parse(second sexp)))]
               [(equal? (first sexp) 'nrest) (nrest (parse(second sexp)))]
               [(equal? (first sexp) 'isnempty) (isnempty (parse(second sexp)))]
               [true (app (first (map parse sexp)) (second (map parse sexp))) ])            ; List of Expr (app expr expr)
             (if (= (length sexp) 3)                     ; processing operators that take only 2 arguments
                 
                 (cond [(not(equal? (lookup-op (first sexp)) false))    ; check if we got a valid operator
                        (bin-num-op (lookup-op (first sexp)) (parse (second sexp))
                                    (parse (third sexp)))]
                       
                       [(equal? (first sexp) 'ncons) (ncons (parse(second sexp)) (parse (third sexp)))]
                       [(equal? (first sexp) 'iszero) (error 'parse "iszero can't take more than 1 argument")]
                       [(equal? (first sexp) 'bif) (error 'parse "bif takes exactly 3 arguments")]
                       [(equal? (first sexp) 'fun) (error 'parse "fun: Invalid syntax")]
                       
                       [(equal? (first sexp) 'with)
                        (if   (symbol? (first (second sexp)))                    
                              
                              (with 
                               (first (second sexp))           ; first element of the first binding
                               (parse (second (second sexp))) ; with parsed seconds element of the first binding
                               (parse (third sexp)))
                              
                              (error 'parse "Not a binding passed to 'with'"))]
                       
                       [(equal? (first sexp) 'fun) (error 'parse "fun takes exactly 4 arguments")]
                       [(Type? (parse (first sexp))) (t-fun (parse(first sexp)) (parse (third sexp)))]
                       [true (error 'parse "Invalid operator name")]                    
                       )
                 (if (= (length sexp) 4)                     ; processing operators that take only 3 arguments
                     (cond  
                       [(equal? (first sexp) 'iszero) (error 'parse "iszero can't take more than 1 argument")]
                       [(equal? (first sexp) 'with) (error 'parse "with can't take more than 2 arguments")]
                       [(not(equal? (lookup-op (first sexp)) false)) (error 'parse "Operator can't take more than two arguments")]
                       [(equal? (first sexp) 'bif) (bif (parse (second sexp)) (parse(third sexp)) (parse (fourth sexp)))]
                       [(equal? (first sexp) 'fun) (error 'parse "fun: Invalid syntax")]
                       [(equal? (first sexp) 'ncons) (error 'parse "ncons can't take more than 2 arguments")]
                       [true (error 'parse "Invalid operator name")])
                     (if (= (length sexp) 5)                     ; processing operators that take only 4 arguments
                         (cond  
                           [(equal? (first sexp) 'iszero) (error 'parse "iszero can't take more than 1 argument")]
                           [(equal? (first sexp) 'with) (error 'parse "with can't take more than 2 argument")]
                           [(not(equal? (lookup-op (first sexp)) false)) (error 'parse "Operator can't take more than two arguments")]
                           [(equal? (first sexp) 'bif) (error 'parse "bif can take exactly 3 arguments only")]
                           [(equal? (first sexp) 'fun)
                            (if (= (length (second sexp)) 3)
                                (fun (first(second sexp)) (parse (third (second sexp))) (parse (fourth sexp)) (parse (fifth sexp)))
                                (error 'parse "fun: Invalid syntax"))]
                           [true (error 'parse "Invalid operator or construct name")])
                         (error 'parse "Operator or construct can't take more than four arguments")
                         )
                     ))))]
    [true (error 'parse "Illegal syntax")]))                           ; illegal datatype argument

;; Tests:
(display "\n====== Parser tests ======\n")
(test/exn (parse "foobar") "Illegal syntax")                                            ;; Test invalid expression - don't parse strings
'num:
(test (parse '5) (num 5))                                                               ;; Test num

'id:
(test (parse 'a) (id 'a))                                                               ;; Test id
(test (parse '!) (id '!))                                                               ;; Weird id test
(test/exn (parse '+) "Invalid id")                                                      ;; Invalid id test
(test/exn (parse 'with) "Invalid id")                                                   ;; Invalid id test

'boolean:
(test (parse 'true) (bool #t))                                                           ;; test boolean
(test (parse 'false) (bool #f))                                                          ;; test boolean
(test (parse '#t) (bool #t))                                                             ;; test boolean
(test (parse '#f) (bool #f))                                                             ;; test boolean

'bin-num-op:
(test (parse '(+ 1 2)) (bin-num-op + (num 1) (num 2)))                                  ;; Test bin-num-op - legal
(test/exn (parse '(! 1 2)) "Invalid operator name")                                     ;; Test bin-num-op - illegal op
(test/exn (parse '(+ 1 2 3)) "Operator can't take more than two arguments")             ;; Test bin-num-op - more args

'iszero:
(test (parse '{iszero 0}) (iszero (num 0)))                                             ;; Test iszero on num
(test (parse '{iszero abc}) (iszero (id 'abc)))                                         ;; Test iszero on id
(test/exn (parse '{iszero 0 1}) "iszero can't take more than 1 argument")               ;; Test is zero with more than 1 argument

'bif:
(test (parse '{bif true false true}) (bif (bool #t) (bool #f) (bool #t)))               ;; Testing bif correct args
(test (parse '{bif #t #f #t}) (bif (bool #t) (bool #f) (bool #t)))                      ;; Testing bif correct args
(test (parse '{bif #t #f #t}) (bif (bool #t) (bool #f) (bool #t)))                      ;; Testing bif correct args
(test/exn (parse '{bif #t}) "bif takes exactly 3 arguments")                            ;; Testing bif invalid number of args
(test/exn (parse '{bif false #f}) "bif takes exactly 3 arguments")                      ;; Testing bif invalid number of args
(test/exn (parse '{bif true #t #f false}) "parse: bif can take exactly 3 arguments only");; Testing bif invalid number of args

'with:
(test (parse '(with [x 1] y)) (with 'x (num 1) (id 'y)))                                ;; with simple test
(test (parse '(with [x 1] (* x 2))) (with 'x (num 1) (bin-num-op * (id 'x) (num 2))))   ;; with expression test
(test (parse '(with (x 5) (+ x x)))  (with 'x (num 5) (bin-num-op + (id 'x) (id 'x))))  ;; one more
(test/exn (parse '(with [3 3] x)) "Not a binding passed to 'with'")                     ;; Malformed binding
(test (parse '(with (x x) 10)) (with 'x (id 'x) (num 10)))                              ;; Scopping test
(test (parse '(with [x 1] (with [y 2] (with [z 3] {- x {+ y z}}))))                     ;; Enclosed 'with' test
      (with 'x (num 1)
            (with 'y (num 2)
                 (with 'z (num 3)
                      (bin-num-op - (id 'x) (bin-num-op + (id 'y) (id 'z)))))))

'fun:
(test/exn (parse '(fun (x) 10)) "fun: Invalid syntax")                                  ;; fun: Invalid syntax
(test/exn (parse '(fun (x : number) : 10)) "fun: Invalid syntax")                       ;; fun: Invalid syntax
(test/exn (parse '(fun (x : number) number 10)) "fun: Invalid syntax")                  ;; fun: Invalid syntax
(test/exn (parse '(fun (x : number) 10)) "fun: Invalid syntax")                         ;; fun: Invalid syntax
(test/exn (parse '(fun (x : number)  : number 10 20)) "Operator or construct can't take more than four arguments");; fun: Invalid syntax
(test/exn (parse '(fun (x) : number 10)) "fun: Invalid syntax")                         ;; fun: Invalid syntax
(test/exn (parse '(fun (x : ) : number 10)) "fun: Invalid syntax")                      ;; fun: Invalid syntax
(test/exn (parse '(fun (x number) : number 10)) "fun: Invalid syntax")                  ;; fun: Invalid syntax
(test (parse '(fun (x : number) : number 10)) (fun 'x (t-num) (t-num) (num 10)))        ;; fun: valid syntax
;(test (parse '(fun (x) (+ x x))) (fun (x) (binop + (id 'x) (id 'x))))                  ;; 

'app:
(test (parse '{x 1}) (app (id 'x) (num 1)))                                             ;; simple app test
(test (parse '{true #f}) (app (bool true) (bool false)))                                ;; simple app test (different datatypes)
(test (parse '{{+ 1 x} false}) (app (bin-num-op + (num 1) (id 'x)) (bool #f)))          ;; complex app test
(test/exn (parse '{x 1 #t}) "Invalid operator name")                                    ;; invalid syntax test
(test/exn (parse '{x 1 #t false})  "Invalid operator name")                             ;; invalid syntax test
(test/exn (parse '{x}) "Invalid syntax: no operator is allowed without arguments")      ;; invalid syntax test

'nempty:
(test (parse 'nempty) (nempty))

'type:
(test (parse 'number) (t-num))
(test (parse 'boolean) (t-bool))
(test (parse 'nlist) (t-nlist))
(test (parse '{boolean -> number}) (t-fun (t-bool) (t-num)))
(test (parse '{{nlist -> boolean} -> number}) (t-fun (t-fun (t-nlist)(t-bool))(t-num)))

'ncons:
(test (parse '{ncons 1 2}) (ncons (num 1) (num 2)))                                     ;; generic cons test
(test (parse '{ncons x false}) (ncons (id 'x) (bool false)))                            ;; another generic cons
(test (parse '{ncons 1 nempty}) (ncons (num 1) (nempty)))                               ;; a real cons
(test (parse '{ncons 1 {ncons 2 nempty}}) (ncons (num 1) (ncons (num 2) (nempty))))     ;; a list
(test/exn (parse '{ncons 1}) "ncons can't take less than 2 arguments")                  ;; malformed cons
(test/exn (parse '{ncons}) "Invalid syntax: no operator is allowed without arguments")  ;; malformed cons
(test/exn (parse '{ncons 1 x #t}) "ncons can't take more than 2 arguments")             ;; malformed cons
(test/exn (parse '{ncons 1 x #t number}) "parse: Invalid operator or construct name")   ;; malformed cons
(test/exn (parse '{ncons 1 x #t false number}) "Operator or construct can't take more than four arguments")   ;; malformed cons

'nfirst:
(test (parse '{nfirst 1}) (nfirst (num 1)))                                                                   ;; generic nfirst
(test/exn (parse '{nfirst 1 2}) "Invalid operator name")                                                      ;; invalid syntax
(test/exn (parse '{nfirst}) "Invalid syntax: no operator is allowed without arguments")                       ;; invalid syntax
(test (parse '{nfirst {ncons 1 {ncons 2 nempty}}}) (nfirst (ncons (num 1) (ncons (num 2) (nempty)))))         ;; real nfirst usage

'nrest:
(test (parse '{nrest 1}) (nrest (num 1)))                                                                     ;; generic nrest
(test/exn (parse '{nrest 1 2}) "Invalid operator name")                                                       ;; invalid syntax
(test/exn (parse '{nrest}) "Invalid syntax: no operator is allowed without arguments")                        ;; invalid syntax
(test (parse '{nrest {ncons 1 {ncons 2 nempty}}}) (nrest (ncons (num 1) (ncons (num 2) (nempty)))))           ;; real nrest usage

'isnempty:
(test (parse '{isnempty 1}) (isnempty (num 1)))                                                                ;; generic isnempty
(test/exn (parse '{isnempty 1 2}) "Invalid operator name")                                                     ;; invalid syntax
(test/exn (parse '{isnempty}) "Invalid syntax: no operator is allowed without arguments")                      ;; invalid syntax
(test (parse '{isnempty {ncons 1 {ncons 2 nempty}}}) (isnempty (ncons (num 1) (ncons (num 2) (nempty)))))      ;; real isnempty usage

'combined:
(test (parse '((fun (x : boolean) : nlist (+ x x)) (ncons 2 nempty)))
      (app (fun 'x (t-bool) (t-nlist)(bin-num-op + (id 'x) (id 'x))) (ncons (num 2) (nempty))))
(test (parse '(with (x (ncons 1 (ncons false nempty))) x)) (with 'x (ncons (num 1) (ncons (bool #f) (nempty))) (id 'x)))
(test (parse '(with [x true] (with [y (ncons 2 (ncons a nempty))] (with [z 3] {- x {+ y z}}))))
      (with 'x (bool true)
            (with 'y (ncons (num 2) (ncons (id 'a) (nempty)))
                 (with 'z (num 3)
                      (bin-num-op - (id 'x) (bin-num-op + (id 'y) (id 'z)))))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Helper function: lookup

;; Contact: lookup : symbol Env -> Type

;; Purpose: Looks up the type of the symbol (passed as the first argument) in the environment (passed as the second argument).

;; Example: see tests

;; Definition [refines the header]
(define (lookup lookup-name env)
  (type-case Env env 
                    [mtEnv () (error 'lookup "no type for identifier")]
                    [anEnv (name value next) (if (symbol=? name lookup-name) value (lookup lookup-name next))]))
;; Tests:
;testing lookup
(test (lookup 'a (anEnv 'a (t-num) (mtEnv))) (t-num))
(test (lookup 'a (anEnv 'b (t-num) (anEnv 'a (t-bool) (mtEnv)))) (t-bool))
(test/exn (lookup 'a (anEnv 'b (t-num) (anEnv 'c (t-bool) (mtEnv)))) "no type for identifier")


;; Contract: type-of : Expr -> Type 

;; Purpose: Consumes the abstract representation of a program. If the program has no type errors, type-of returns the type of the program.
;; If the program has a type error, type-of should invoke error with an appropriate error message.

;; Example:  (type-of (parse '{+ 1 2})) should produce (t-num), while (type-of (parse '{3 4})) should call (error 'type-of "NUmber is not a function").

;; Definition [refines the header]
(define (type-of e)
  (type-ofE e (mtEnv)))

;; Tests:
;; see below


;; Helper function: type-ofE

;; Contract: type-ofE : Expr Env -> Type 

;; Purpose: Consumes the abstract representation of a program and a type environment. If the program has no type errors, type-of returns the type of the program.
;; If the program has a type error, type-of should invoke error with an appropriate error message.

;; Example: see tests

;; Definition [refines the header]
(define (type-ofE e env) 
  (type-case Expr e
             [num (n) (t-num)]
             [id (i) (lookup i env)]
             [bool (b) (t-bool)]
             [bin-num-op (op lhs rhs) (if (and (equal? (type-ofE lhs env) (t-num)) (equal? (type-ofE rhs env) (t-num))) 
                                          (t-num) 
                                          (error "binop given non-number input"))] 
             [iszero (ex) (if (equal? (type-ofE ex env) (t-num)) 
                              (t-bool) 
                              (error "iszero expects num, given something else"))]
             [bif (test then else) (if (and (equal? (type-ofE test env) (t-bool)) (equal? (type-ofE then env) (type-ofE else env))) 
                                       (type-ofE then env) 
                                       (if (equal? (type-ofE test env) (t-bool)) (error "bif's branches return different types") (error "bif's conditional does not return a boolean value")))] 
             [with (bound-id bound-body body) (type-ofE body (anEnv bound-id (type-ofE bound-body env) env))]
             [fun (arg-id arg-type result-type body) (if (equal? (type-ofE body (anEnv  arg-id arg-type  env)) result-type) 
                                                         (t-fun arg-type result-type)
                                                         (error "function does not return the type promised"))];#|(t-fun arg-type result-type)|# add to local-env: arg-id is type it should be, verify body returns what it says it should
             [app (app-fun-expr app-arg-expr) (type-case Expr app-fun-expr
                                                 [fun (arg-id arg-type result-type body) (if (equal? arg-type (type-ofE app-arg-expr env)) 
                                          result-type
                                          (error "app: arg-expr's type does not match fun-expr-arg-type"))]
                                                 [else (error "error in calling app, weirdo")])];verify app is passed what it expects, then return what app says it would
             [nempty () (t-nlist)]
             [ncons (first rest) (if (and (equal? (type-ofE first env) (t-num)) (equal? (type-ofE rest env) (t-nlist))) 
                                     (t-nlist)
                                     (if (equal? (type-ofE first env) (t-num)) 
                                         (error "in ncons, rest is not another (t-nlist)")
                                         (error "in ncons, first is not a number")))]
             [nfirst (ex) (if (equal? (type-ofE ex env) (t-nlist)) (t-num) (error "calling nfirst on non-list"))]
             [nrest (ex) (if (equal? (type-ofE ex env) (t-nlist)) (t-nlist) (error "calling nrest on non-list"))]
             [isnempty (ex) (if (equal? (type-ofE ex env) (t-nlist)) (t-bool) (error "calling isnempty on non-list"))]))

;; Tests:
(display "\n=== type-ofE tests ===\n")
(test (type-ofE (num 1) (mtEnv)) (t-num))
(test (type-ofE (bool false)(mtEnv)) (t-bool))


;; Tests [type-of]:
(display "\n=== type-of tests ===\n")
(test (type-of (num 1)) (t-num))
(test (type-of (bool false)) (t-bool))

"testing binops"
"addition succeed and fail"
(test (type-of (bin-num-op + (num 2) (num 3))) (t-num))            
(test/exn (type-of (bin-num-op + (num 2) (bool false))) "binop given non-number input")
"subtraction succeed and fail"
(test (type-of (bin-num-op - (num 2) (num 3))) (t-num))            
(test/exn (type-of (bin-num-op - (num 2) (bool false))) "binop given non-number input")
"multiplicaiton succeed and fail"
(test (type-of (bin-num-op * (num 2) (num 3))) (t-num))            
(test/exn (type-of (bin-num-op * (num 2) (bool false))) "binop given non-number input")
"nested binops succeed and fail"
(test (type-of (bin-num-op + (bin-num-op - (num 3) (num 10)) (bin-num-op - (bin-num-op * (num 1) (num 2)) (num 3)))) (t-num)) 
(test/exn (type-of (bin-num-op + (bin-num-op - (num 3) (bool true)) (bin-num-op - (bin-num-op * (num 1) (num 2)) (num 3)))) "binop given non-number input") 
'iszero
(test (type-of (iszero (num 2))) (t-bool)) 
(test (type-of (iszero (num 0))) (t-bool))
(test/exn (type-of (iszero (bool false))) "iszero expects num, given something else")
(test (type-of (iszero (bin-num-op + (num 2) (num 10)))) (t-bool))
'bif
(test (type-of (bif (bool true) (num 2) (num 3))) (t-num))
(test (type-of (bif (bool false) (bool true) (bool false))) (t-bool))
(test/exn (type-of (bif (num 2) (num 3) (num 4))) "bif's conditional does not return a boolean value")
(test/exn (type-of (bif (bool false) (bool true) (num 4))) "bif's branches return different types")
;empty
(test (type-of (nempty)) (t-nlist))
'ncons
(test (type-of (ncons (num 2) (nempty))) (t-nlist))
(test (type-of (ncons (num 4) (ncons (num 5) (nempty)))) (t-nlist))
(test/exn (type-of (ncons (bool false) (nempty))) "in ncons, first is not a number")
(test/exn (type-of (ncons (num 1) (num 2))) "in ncons, rest is not another (t-nlist)")
'nfirst
(test (type-of (nfirst (ncons (num 2) (nempty)))) (t-num))
(test/exn (type-of (nfirst (num 2))) "calling nfirst on non-list")
'nrest
(test (type-of (nrest (ncons (num 2) (nempty)))) (t-nlist))
(test/exn (type-of (nrest (num 2))) "calling nrest on non-list")   
'isnempty
(test (type-of (isnempty (ncons (num 2) (nempty)))) (t-bool))
(test/exn (type-of (isnempty (num 2))) "calling isnempty on non-list") 
'fun
(test (type-of (fun 'x (t-num) (t-bool) (bool true))) (t-fun (t-num) (t-bool)))
(test (type-of (fun 'x (t-num) (t-nlist) (ncons (id 'x) (nempty)))) (t-fun (t-num) (t-nlist)))
'app
(test (type-of (app (fun 'x (t-num) (t-bool) (id 'x)) (num 25))) (t-bool))
(test (type-of (app (fun 'x (t-bool) (t-num) (id 'x)) (bool true))) (t-num)) 

;; Combined parse/type-of tests:
(display "\n=== Combined parse/type-of tests ===\n")

'num:
(test (type-of (parse '1)) (t-num))

'bool:
(test (type-of (parse 'true)) (t-bool))
(test (type-of (parse '#f)) (t-bool))

'bin-num-op:
(test (type-of (parse '{+ 1 2})) (t-num))
(test (type-of (parse '{- 1 2})) (t-num))
(test (type-of (parse '{* 1 2})) (t-num))
(test/exn (type-of (parse '{- x 2})) "no type for identifier")
(test/exn (type-of (parse '{+ 1 true})) "binop given non-number input")
(test/exn (type-of (parse '{+ 1 nempty})) "binop given non-number input")
(test/exn (type-of (parse '{+ 1 (ncons 1 nempty)})) "binop given non-number input")

'iszero:
(test (type-of (parse '{iszero 1})) (t-bool))
(test (type-of (parse '{iszero 0})) (t-bool))
(test/exn (type-of (parse '{iszero x})) "lookup: no type for identifier")
(test/exn (type-of (parse '{iszero true})) "iszero expects num, given something else")
(test/exn (type-of (parse '{iszero false})) "iszero expects num, given something else")
(test/exn (type-of (parse '{iszero nempty})) "iszero expects num, given something else")
(test/exn (type-of (parse '{iszero (ncons 1 nempty)})) "iszero expects num, given something else")

'bif:
(test/exn (type-of (parse '{bif 1 2 3})) "bif's conditional does not return a boolean value")
(test/exn (type-of (parse '{bif nempty 2 3})) "bif's conditional does not return a boolean value")
(test/exn (type-of (parse '{bif (1 2) 2 3})) "error in calling app, weirdo")
(test (type-of (parse '{bif true 2 3})) (t-num))
(test (type-of (parse '{bif #t false #t})) (t-bool))
(test/exn (type-of (parse '{bif hello false #t})) "lookup: no type for identifier")
(test/exn (type-of (parse '{bif #t hello goodbyu})) "lookup: no type for identifier")
(test (type-of (parse '{bif #t nempty (ncons 1 nempty)})) (t-nlist))
(test (type-of (parse '{bif false nempty (ncons 1 nempty)})) (t-nlist))

'with: 
(test (type-of (parse '{with [x 1] x})) (t-num))
(test (type-of (parse '{with [x true] x})) (t-bool))
(test (type-of (parse '{with [x nempty] x})) (t-nlist))
(test (type-of (parse '{with [x (ncons 1 nempty)] x})) (t-nlist))
(test (type-of (parse '{with [x (ncons 1 nempty)] {with [y 2] {with [z 3] {+ z {- y (nfirst x)}}}}})) (t-num))             ;;enclosed with
(test/exn (type-of (parse '{with [x (ncons 1 nempty)] {with [y 2] {with [z 3] {+ z {- y x}}}}})) "binop given non-number input");;enclosed with
(test (type-of (parse '{with [x false] {with [y 2] {with [z (ncons 1 nempty)] {bif x y (nfirst z)}}}})) (t-num))            ;;combined with/bif

'fun:
(test (type-of (parse '{fun (x : boolean) : number (bif x 10 15)})) (t-fun (t-bool) (t-num)))  
(test (type-of (parse '{fun (x : boolean) : number 10})) (t-fun (t-bool) (t-num)))
(test/exn (type-of (parse '{fun (x : number) : boolean x})) "function does not return the type promised")
(test (type-of (parse '{fun (x : number) : nlist (ncons x nempty)})) (t-fun (t-num) (t-nlist))) 
(test (type-of (parse '{fun (x : number) : boolean (bif (iszero x) true false)})) (t-fun (t-num) (t-bool)))
(test/exn (type-of (parse '{fun (x : number) : boolean (bif (iszero x) 1 0)}))  "function does not return the type promised")


'app:
(test (type-of (parse '({fun (x : number) : boolean x} 5))) (t-bool))                                         
(test (type-of (parse '({fun (x : number) : boolean (bif (iszero x) true false)} (bif (iszero 2) 3 4)))) (t-bool))
(test/exn (type-of (parse '({fun (x : number) : boolean x} false))) "app: arg-expr's type does not match fun-expr-arg-type")

'nempty:
(test (type-of (parse 'nempty)) (t-nlist))

'ncons:
(test (type-of (parse '{ncons 1 nempty})) (t-nlist))                                   ;; Simple ncons
(test/exn (type-of (parse '{ncons 1 2})) "in ncons, rest is not another (t-nlist)")    ;; Malformed ncons
(test/exn (type-of (parse '{ncons true nempty})) "in ncons, first is not a number")    ;; Malformed ncons
(test/exn (type-of (parse '{ncons x nempty})) "lookup: no type for identifier")        ;; out of scope id
(test (type-of (parse '{with [x 2] {ncons x nempty}})) (t-nlist))                      ;; combined with 'with'
(test (type-of (parse '{ncons -1 {ncons -2 nempty}})) (t-nlist))                       ;; a list

'nfirst:
(test (type-of (parse '{nfirst nempty})) (t-num))                                      ;; type correct - runtime error
(test (type-of (parse '{nfirst (ncons 2 nempty)})) (t-num))                            ;; nonempty list
(test/exn (type-of (parse '{nfirst 10})) "calling nfirst on non-list")                 ;; wrong argument


'nrest:
(test (type-of (parse '{nrest nempty})) (t-nlist))                                     ;; type correct - runtime error
(test (type-of (parse '{nrest (ncons 2 nempty)})) (t-nlist))                           ;; nonempty list
(test/exn (type-of (parse '{nrest 2})) "calling nrest on non-list")                    ;; wrong argument

'isnempty:
(test (type-of (parse '{isnempty nempty})) (t-bool))                                   ;; trivial case - empty list
(test (type-of (parse '{isnempty (ncons 2 nempty)})) (t-bool))                         ;; nonempty list
(test/exn (type-of (parse '{isnempty 2})) "calling isnempty on non-list")              ;; wrong argument
(test/exn (type-of (parse '{isnempty false})) "calling isnempty on non-list")          ;; wrong argument